package controllers

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"im/app/conf"
	"im/app/models"
	"im/app/util/response"
	"log"
	"net/http"
	"sync"
	"time"
)

type Chat struct {

}

// 消息结构体
type Message struct {
	Id      int  `json:"id,omitempty" form:"id"`           //消息ID
	MsgId   string `json:"msg_id" form:"msg_id"`    // 消息编号
	Userid  int  `json:"user_id,omitempty" form:"user_id"`   //谁发的
	Cmd     int    `json:"cmd,omitempty" form:"cmd"`         //群聊还是私聊
	Dstid   int  `json:"dst_id,omitempty" form:"dst_id"`     //对端用户ID/群ID
	Content string `json:"content,omitempty" form:"content"` //消息的内容
	Pic     string `json:"pic,omitempty" form:"pic"`         //预览图片
}

type Node struct {
	Conn *websocket.Conn
	//并行转串行,
	DataQueue chan []byte
}

//userid和Node映射关系表
var ClientMap sync.Map
// 用户判断user是否在线
var ClientMapOnline sync.Map

// 协议升级
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// 聊天
func (ct *Chat) Chat (c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		response.ReturnJsonOk(c, "wk连接失败")
		return
	}
	user,_ := c.Get(models.ContextKeyUserObj)
	userOb := user.(*models.User)
	//获得websocket链接conn
	node := &Node{
		Conn:      conn,
		DataQueue: make(chan []byte, 100),
		//GroupSets: set.New(set.ThreadSafe),
	}
	// 存储在线用户连接
	ClientMap.Store(userOb.ID, node)
	ClientMapOnline.Store(userOb.ID, true)

	// 接受消息
	go recvproc(node)
	// 发送消息
	go sendproc(node)
	// 心跳检测
	go heart()
}

// 心跳检测
func heart() {
	t1 := time.NewTicker(time.Second * 300)
	go func() {
		for {
			select {
			case <-t1.C:
				//检测客户端的心跳
				ClientMapOnline.Range(func(k, v interface{}) bool {
					if v == false {
						ClientMap.Delete(k)
					}
					return true
				})
			}
		}
	}()
}

// 根据消息id获取消息是否已经已经接收到s
func getAckMsgById(msgId string) bool {
	messsage := models.Messages{}
	conf.Mdb.Where("msg_id=?", msgId).Take(&messsage)
	if messsage.IsR > 0 {
		return true
	}
	return false
}

// 根据user_id判断用户是否在线
func isLive(userId int) bool {
	if _,ok := ClientMap.Load(userId); ok {
		return true
	}
	return false
}

//定义命令行格式
const (
	CmdPingPong = 1
	CmdHeart     = 2   // 心跳
	CmdBroadcast = 3   // 广播
	CmdSingleMsg = 10   // 单个消息
	CmdSingleMsgAck = 11   // 单个消息ack
	CmdGroupMsg   = 21  // 群消息
)

//后端调度逻辑处理
func dispatch(data []byte) {
	msg := Message{}
	err := json.Unmarshal(data, &msg)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}
	switch msg.Cmd {
	case CmdPingPong:
		msg.Content = "pong"
		msg.Cmd = 1
		dd,_ := json.Marshal(&msg)
		sendMsg(msg.Userid, dd)

	case CmdBroadcast:
		ClientMap.Range(func(k, v interface{}) bool {
			v.(*Node).DataQueue <- data
			return true
		})

	case CmdHeart:
		ClientMapOnline.Store(msg.Userid, true)
		msg.Content = "pong"
		msg.Cmd = 2
		dd,_ := json.Marshal(&msg)
		sendMsg(msg.Userid, dd)

	case CmdSingleMsg:
		// 给发送者发一条消息ack
		sendMsg(msg.Userid, data)
		fmt.Println("消息发送给发送者")
		// 给对方发一条消息
		sendMsg(msg.Dstid, data)
		fmt.Println("消息发送给自己")
		// 重发定时器
		timer := time.NewTicker(time.Second * 120)
		go func() {
			for {
				select {
				// 其它case
				case <- timer.C :
					fmt.Println("消息发送超时")
					// 判断用户是否在线了
					if !isLive(msg.Dstid) {
						timer.Stop()
						return
					}
					// 判断消息是否已经收到了
					if !getAckMsgById(msg.MsgId) {
						sendMsg(msg.Dstid, data)
					}else {
						timer.Stop()
						return
					}
				}
			}
		}()

	case CmdSingleMsgAck:
		// 给发送者发一条消息ack
		sendMsg(msg.Userid, data)
		// 给对方发一条消息
		sendMsg(msg.Dstid, data)

	case CmdGroupMsg:
		ClientMap.Range(func(k, v interface{}) bool {
			v.(*Node).DataQueue <- data
			return true
		})

	}
}

//发送消息,发送到消息的管道
func sendMsg(userId int, msg []byte) {
	if node, ok := ClientMap.Load(userId); ok {
		node.(*Node).DataQueue <- msg
	}
}

//发送逻辑
func sendproc(node *Node) {
	for {
		select {
		case data := <-node.DataQueue:
			err := node.Conn.WriteMessage(websocket.TextMessage, data)
			if err != nil {
				log.Println(err.Error())
				return
			}
			// 消息发送成功
		}
	}
}

//接收逻辑
func recvproc(node *Node) {
	for {
		_, data, err := node.Conn.ReadMessage()
		if err != nil {
			log.Println(err.Error())
			return
		}
		// 消息分发
		dispatch(data)
		//todo对data进一步处理
		fmt.Printf("recv<=%s", data)
	}
}

